package auth

import (
	"context"
	"crypto/ecdsa"
	"sync"
	"time"
)

type inmemoryUser struct {
	id       int64
	name     string
	account  string
	password string
	access   []Access
}

func (i *inmemoryUser) Accesses() []Access {
	return i.access
}

func (i *inmemoryUser) Account() string {
	return i.account
}

func (i *inmemoryUser) VerifyPassword(password string) bool {
	return i.password == password
}

func (i *inmemoryUser) Id() interface{} {
	return i.id
}

func (i *inmemoryUser) Name() string {
	return i.name
}

type inmemoryUserDataSource struct {
	users    map[string]User
	accesses map[int64][]Access
}

func (i *inmemoryUserDataSource) FetchUser(ctx context.Context, userAccount string) User {
	return i.users[userAccount]
}

func newInmemoryUserDataSource(userPasswords map[string]string, accesses map[string][]Access) DataSource {
	users := make(map[string]User)
	accessIdMap := make(map[int64][]Access)
	var id int64 = 1
	for account, password := range userPasswords {
		users[account] = &inmemoryUser{
			id:       id,
			account:  account,
			password: password,
			name:     account,
		}
		if as, ex := accesses[account]; ex {
			accessIdMap[id] = as
		}
		id++
	}
	return &inmemoryUserDataSource{
		users:    users,
		accesses: accessIdMap,
	}
}

type inmemoryPublicKeyStore struct {
	sync.Mutex
	publicKeys        map[string]*KeyInfo
	cancelCleanupWait func()
}

func newInThreadPublicKeyStore() *inmemoryPublicKeyStore {
	store := &inmemoryPublicKeyStore{
		publicKeys: make(map[string]*KeyInfo),
	}
	//store.cleanUp(context.Background())
	return store
}

func (i *inmemoryPublicKeyStore) SavePublicKey(ctx context.Context, info *KeyInfo) error {
	i.Lock()
	defer i.Unlock()
	i.publicKeys[info.KeyId] = info
	return nil
}

func (i *inmemoryPublicKeyStore) LoadPublicKey(ctx context.Context, keyId string) *ecdsa.PublicKey {
	i.Lock()
	defer i.Unlock()
	if keyInfo, ok := i.publicKeys[keyId]; ok {
		return keyInfo.Key
	}
	return nil
}

func (i *inmemoryPublicKeyStore) cleanUp(ctx context.Context) {
	i.Lock()
	defer i.Unlock()
	minTTL := 0 * time.Second
	now := time.Now()
	for keyId, keyInfo := range i.publicKeys {
		if keyTTL := keyInfo.ExpireAt.Sub(now); keyTTL < 0 {
			delete(i.publicKeys, keyId)
		} else if minTTL == 0 || keyTTL < minTTL {
			minTTL = keyTTL
		}
	}
	if minTTL <= 0 {
		minTTL = 0
	}
	if i.cancelCleanupWait != nil {
		i.cancelCleanupWait()
	}
	ctxWithCancel, cancel := context.WithCancel(ctx)
	i.cancelCleanupWait = cancel
	go cleanupWait(ctxWithCancel, minTTL+5*time.Minute, i)
}

func cleanupWait(ctx context.Context, ttl time.Duration, store *inmemoryPublicKeyStore) {
	cleanTimer := time.NewTimer(ttl)
	select {
	case <-ctx.Done():
		return
	case <-cleanTimer.C:
		go store.cleanUp(ctx)
	}
}
